import * as readline from "node:readline";
import { TestRunner } from "./test-runner";
import { StressArgs } from "../index";

enum Answers {
    TEST_TX = "1",
    TEST_CONTRACTS = "2",
    EDIT_URL = "3",
    EDIT_PKEY = "4",
    EDIT_ADDRESS = "5",
    EDIT_TRANSACTIONS_AMOUNT = "6",
    EDIT_ACCOUNTS_AMOUNT = "7",
    EXIT = "0",
}

export class TestFactory {
    private testRunner: TestRunner;
    private url: string;
    private senderPrivateKey: string;
    private receiverAddress: string;
    private txAmount = 100;
    private accAmount = 10;
    private rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });

    constructor(args: StressArgs) {
        this.url = args.node;

        this.senderPrivateKey = args.senderPrivateKey;
        this.receiverAddress = args.receiverAddress;
        this.txAmount = args.transactions;
        this.accAmount = args.accounts;

        this.testRunner = new TestRunner(this.url, this.senderPrivateKey, this.receiverAddress, this.txAmount, this.accAmount);
    }

    private getPromptText(): string {
        let promptText = `\nSelect one of the following options:\n`;
        promptText += `\t${Answers.TEST_TX}: Start transactions stress test!\n`;
        promptText += `\t${Answers.TEST_CONTRACTS}: Start contracts stress test!\n`;
        promptText += `\t${Answers.EDIT_URL}: Edit node url (${this.url})\n`;
        promptText += `\t${Answers.EDIT_PKEY}: Edit sender private key (${this.senderPrivateKey})\n`;
        promptText += `\t${Answers.EDIT_ADDRESS}: Edit receiver address (${this.receiverAddress})\n`;
        promptText += `\t${Answers.EDIT_TRANSACTIONS_AMOUNT}: Edit transactions amount (${this.txAmount})\n`;
        promptText += `\t${Answers.EDIT_ACCOUNTS_AMOUNT}: Edit accounts amount (${this.accAmount})\n`;
        promptText += `\t${Answers.EXIT}: Exit\n`;

        return promptText;
    }

    private async runTransactionsTest(): Promise<void> {
        this.rl.write("\nRunning transactions test...\n");

        try {
            const startTime = Date.now();
            await this.testRunner.runTransactions();
            const endTime = Date.now();

            const totalTxs = this.txAmount * this.accAmount + this.accAmount;
            const txPerSecond = (totalTxs * 1000) / (endTime - startTime);
            this.rl.write(`Total transactions:\n\t${totalTxs}\n`);
            this.rl.write(`Transactions per second:\n\t${txPerSecond}\n`);
        } catch (error: any) {
            console.log("runTransactions error:");
            console.log(error);
        }
    }

    private async runContractsTest(): Promise<void> {
        this.rl.write("\nRunning contracts test...\n");

        try {
            const startTime = Date.now();
            await this.testRunner.runContracts();
            const endTime = Date.now();

            const totalTxs = this.txAmount * this.accAmount;
            const txPerSecond = (totalTxs * 1000) / (endTime - startTime);
            this.rl.write(`Contracts deployed:\n\t${this.accAmount}\n`);
            this.rl.write(`Contract calls done per second:\n\t${txPerSecond}\n`);
        } catch (error: any) {
            console.log("runContracts error:");
            console.log(error);
        }
    }

    private async askQuestion(question: string): Promise<string> {
        return new Promise((resolve) => {
            this.rl.question(question, (answer: string) => {
                resolve(answer);
            });
        });
    }

    async start(): Promise<void> {
        let answer = "";
        while (answer !== Answers.EXIT) {
            answer = await this.askQuestion(this.getPromptText());

            switch (answer) {
                case Answers.EDIT_URL:
                    this.url = await this.askQuestion("\nEnter complete node url:\n");
                    this.testRunner.setUrl(this.url);
                    break;
                case Answers.EDIT_PKEY:
                    this.senderPrivateKey = await this.askQuestion("\nEnter sender private key:\n");
                    this.testRunner.setPKey(this.senderPrivateKey);
                    break;
                case Answers.EDIT_ADDRESS:
                    this.receiverAddress = await this.askQuestion("\nEnter receiving address:\n");
                    this.testRunner.setAddress(this.receiverAddress);
                    break;
                case Answers.EDIT_TRANSACTIONS_AMOUNT:
                    this.txAmount = parseInt(await this.askQuestion("\nEnter transactions amount:\n"), 10);
                    this.testRunner.setTxAmount(this.txAmount);
                    break;
                case Answers.EDIT_ACCOUNTS_AMOUNT:
                    this.accAmount = parseInt(await this.askQuestion("\nEnter accounts amount:\n"), 10);
                    this.testRunner.setAccAmount(this.accAmount);
                    break;
                case Answers.TEST_TX:
                    await this.runTransactionsTest();
                    break;
                case Answers.TEST_CONTRACTS:
                    await this.runContractsTest();
                    break;
                case Answers.EXIT:
                    this.rl.write("\nClosing test...\n");
                    break;
                default:
                    this.rl.write("\nInvalid answer.\n");
                    break;
            }
        }

        this.rl.close();
    }
}
